1

臆想的

let fs = require('fs')

function readFile(filename){
    ...
}

let content = readFile('config.js')
// 针对读取到的内容进行操作,比如打印文件内容
console.log(content)

臆想中,读取文件是有返回值的,将返回值,即文件内容,赋给一个变量,然后决定对读取到的内容进行相应的操作,例如打印文件中的内容。

简而言之,臆想中,读取文件,打印文件是相互分开的。

回调

let fs = require('fs')

function readFile(filename, callback){
    fs.readFile(filename, 'utf8', (err, data) => {
        if(err){
            throw new err
        }
        callback(data)
    })
}

readFile('config.js', data => {
    console.log(data)
})

实际上,在经常使用的回调中,读取文件和针对文件内容相应的操作是在一起的,

你在要求读取文件的同时,还要说明获取文件内容后干嘛

这和习惯性思维,你先把文件内容给我,至于我怎么处理,稍后再说

Promise

let fs = require('fs')

function readFile(filename) {
    return new Promise(function (resolve) { // 这里的callback,是在run函数中传递的
        fs.readFile(filename, 'utf8', (err, data) => {
            if (err) {
                reject(err)
            }
            resolve(data)
        })
    })
}

let content = readFile('config.js')
content.then(res => {
    console.log(res)
})
// 对比臆想中的
let content = readFile('config.js')
// 针对读取到的内容进行操作,比如打印文件内容
console.log(content)

使用Promise后,整个书写逻辑开始和臆想中的很接近了,读取文件和对文件内容的操作分开了

即通过使用Promise,可以将异步的操作和对异步结果的处理,分开

来实现一个简陋的假的Promise

let fs = require('fs')

function resolve(value) {
    let _self = this
    setTimeout(function () {
        _self.callbacks.forEach(function (callback) {
            callback(value);
        })
    }, 0)
    // 保证在resolve执行之前,then方法都已经注册
}

class FakePromise {

    constructor(fn) {
        // fn是个函数,里面包含异步,异步成功
        // Promise在new的过程中就已经开始执行异步代码
        // 异步代码执行完触发resolve,resolve作为异步代码的参数,它早已经实现好
        this.value = null
        this.callbacks = []
        fn(resolve.bind(this))
    }

    then(onFulfilled) {
        this.callbacks.push(onFulfilled)
        return this
    }
}

function readFile(filename) {
    return new FakePromise(function (resolve) {
        fs.readFile(filename, 'utf8', (err, data) => {
            if (err) {
                return
            }
            resolve(data)
        })
    })
}

let content = readFile('config.js')
content.then(res => {
    console.log(res)
})

这样看来,Promise和发布-订阅模式有些相像。promise内部也有个事件队列,通过then注册事件,通过resolve触发。每个promise在创建的时候,就开始执行传递给它的函数,函数中会触发resolve,这个resolve就是去执行所有注册的事件。

当然,实际上promise比这强大的多,首先resolve执行所有注册的事件会保证滞后执行,避免还没通过then注册完事件,resolve就执行了

其次,在异步操作成功之后,通过then注册事件,可以立马执行,这就需要给promise添加状态机制

...
then(onFulfilled) {
        if (this.state === 'pending') {
            this.callbacks.push(onFulfilled)
            return this
        }
        onFulfilled(value)
        return this
}
...

判断不是'pending',就立刻执行注册的函数

另外就是每个then()方法,都会返回一个新的promise

大概长这样

then(onFulfilled) {
    return new Promise(...)
}

参考资料

生成器

let fs = require('fs')

function run(taskDef){
    // 传入的taskDef是个生成器函数,执行后返回迭代器
    let task = taskDef()
    // 调用迭代器的next()方法,开始taskDef函数中的代码,直至遇到yield,并将yield的值赋予result
    let result = task.next()
    function step(){
      if(!result.done){
        if(typeof result.value === 'function'){
          result.value((err, data)=>{
            if(err){
              result = task.throw(err)
              return
            }
            result = task.next(data)
            step()
          })
          // 这里的result.value(...){...}
          // 调用的是function(callback){fs.readFile(filename, 'utf8', callback)}
        }else{
          result = task.next(data)
          step() // 判断任务是否执行完
        }
      }
    }
    step()
    // task.next(data) 继续执行生成器中的代码,并将值传回给触发这次next()的yield的等号左边的变量
}

function readFile(filename){
  return function (callback){ // 这里的callback,是在run函数中传递的
    fs.readFile(filename, 'utf8', callback)
  }
}

run(function*(){
  let content = yield readFile('config.js') // 传递函数至run函数中,并由run传递参数调用
  // 对文件内容进行处理
  console.log(content)
})
// 对比臆想中的
let content = readFile('config.js')
// 针对读取到的内容进行操作,比如打印文件内容
console.log(content)

生成器函数可以停止函数执行,代码在yield readFile('copy.js')处暂停

异步任务的回调中调用迭代器的next()方法,使生成器函数中的代码继续执行,并通过next()方法传递参数回至生成器函数中,异步任务完成,返回值已经赋值给了content

Promise+generator

let fs = require('fs')

function run(taskDef){
    // 传入的taskDef是个生成器函数,执行后返回迭代器
    let task = taskDef()
    // 调用迭代器的next()方法,开始taskDef函数中的代码,直至遇到yield,并将yield的值赋予result
    let result = task.next()
    function step(){
      if(!result.done){
        let promise = Promise.resolve(result.value)
        promise.then(value => {
            result = task.next(value)
            step()
        }).catch(err => {
            result = task.throw(err)
            step()
        })
          
      }
    }
    step()
    // task.next(data) 继续执行生成器中的代码,并将值传回给触发这次next()的yield的等号左边的变量
}

function readFile(filename){
  return new Promise(function (resolve, reject){ // 这里的callback,是在run函数中传递的
    fs.readFile(filename, 'utf8', (err, data) => {
        if(err){
            reject(err)
        }
        resolve(data)
    })
  })
}

run(function*(){
  let content = yield readFile('config.js') // 传递函数至run函数中,并由run传递参数调用
  // 对文件内容进行处理
  console.log(content)
})

console.log('先执行')

Async&await

是不是和promise+generator很像

let fs = require('fs')

function readFile(filename) {
    return new Promise(function (resolve) { // 这里的callback,是在run函数中传递的
        fs.readFile(filename, 'utf8', (err, data) => {
            if (err) {
                reject(err)
            }
            resolve(data)
        })
    })
}
(async function test() {
    let content = await readFile('copy.js')
    console.log(content)
})()

参考资料

  • 深入理解ES6

nbb3210
436 声望31 粉丝

优雅地使用JavaScript解决问题